<?php /** @noinspection PhpUnused */

namespace paper\helper;

use paper\exception\ValidateException;

class Validate
{
    private $errMsg = '';
    private $data;
    //设置规则应用范围
    private $scene = null;
    private array $_message = [
        'require' => '[FIELD] 不能为空',
        'number' => '[FIELD] 只能是数字',
        'phone' => '[FIELD] 不是有效的手机号码',
        'mail' => '[FIELD] 不是邮箱的邮箱格式',
        'bool' => '[FIELD] 只能是布尔值',
        'array' => '[FIELD] 只能是数组',
        'integer' => '[FIELD] 只能是整形',
        'maxLen' => '[FIELD] 长度必须小于等于[VALUE]',
        'minLen' => '[FIELD] 长度必须大于等于[VALUE]',
        'max' => '[FIELD] 必须小于等于[VALUE]',
        'min' => '[FIELD] 必须大于等于[VALUE]',
        'url' => '[FIELD] 不是邮箱的url',
        'ip' => '[FIELD] 不是有效的IP地址',
        'alphaDash' => '[FIELD] 只能只字母数组与 - 和 _ 符号',
        'alphaNum' => '[FIELD] 只能是字母与数字',
        'captcha' => '[FIELD] 验证码错误',
    ];

    protected array $friendlyName = []; //定义友好的字段名称
    protected array $rules = [];  //设置规则
    protected array $message = []; //自定义错误提示信息


    protected bool $exception = false;
    private $fails = true;

    public function __construct($rules = [], $data = [])
    {
        $this->rules = $rules;
        if ($data) {
            $this->fails = !$this->check($data) ? $this->getErrMsg() : false;
        }
    }

    /**
     * @return bool|string
     */
    public function fails()
    {
        return $this->fails;
    }

    /**
     * @return array
     */
    public function rules(): array
    {
        return $this->rules;
    }

    /**
     * @return null
     */
    public function getScene()
    {
        return $this->scene;
    }

    /**
     * @param null $scene
     */
    public function setScene($scene): void
    {
        $this->scene = $scene;
    }

    /**
     * 设置规则
     * @param $rules
     */
    public function setRules($rules)
    {
        $this->rules = array_merge($this->rules, $rules);
    }

    /**
     * 获取错误信息
     * @return string
     */
    public function getErrMsg()
    {
        return $this->errMsg;
    }

    /**
     * @param string $field
     * @param string $rule
     * @param string $params
     * @param null $fieldName
     */
    private function setErrMsg(string $field, string $rule, $params = '', $fieldName = null): void
    {
        $msgKey = $field . '.' . $rule;
        if (isset($this->message[$msgKey])) {
            $this->errMsg = $this->message[$msgKey];
            if ($this->exception)
                throw new ValidateException($this->errMsg);
            return;
        }
        $this->errMsg = str_replace("[FIELD]", $fieldName, $this->_message[$rule]);
        $this->errMsg = str_replace("[VALUE]", $params, $this->errMsg);
    }


    /**
     * 设置用户自定义错误消息
     * @param array $message
     */
    public function setMessage(array $message): void
    {
        $this->message = $message;
    }

    /**
     * 判断是否是手机号码
     * @param $value
     * @return bool
     */
    protected function validatePhone($value): bool
    {
        if (preg_match("/^[0-9]\d{10}$/", $value)) {
            return true;
        }
        return false;
    }

    /**
     * 验证图像验证码
     * @param $value
     * @return bool
     */
    protected function validateCaptcha($value): bool
    {
        return Captcha::validate($value);
    }

    protected function validateArray($value): bool
    {
        return is_array($value);
    }

    /**
     * 判断是否是整形
     * @param $value
     * @return bool
     */
    protected function validateInteger($value): bool
    {
        //is_int 只能判断变量是否整形但是不能判断字符串
        return is_numeric($value) && is_int($value + 0);
    }

    /**判断最大值
     * @param $value
     * @param $params
     * @return bool
     */
    protected function validateMax($value, $params): bool
    {
        $params = (int)$params;
        return $value <= $params;
    }

    /**判断最小值
     * @param $value
     * @param $params
     * @return bool
     */
    protected function validateMin($value, $params): bool
    {
        $params = (int)$params;
        return $value >= $params;
    }

    /**
     * 判断是否邮箱
     * @param $value
     * @return bool
     */
    protected function validateMail($value): bool
    {
        return (bool)filter_var($value, FILTER_VALIDATE_EMAIL);
    }

    /**
     * 判断是否url
     * @param $value
     * @return bool
     */
    protected function validateUrl($value): bool
    {
        return (bool)filter_var($value, FILTER_VALIDATE_URL);
    }

    /**
     * 判断是否字母+数字+_-
     * @param $value
     * @return bool
     */
    protected function validateAlphaDash($value): bool
    {
        return preg_match('/^([0-9a-zA-Z_\-]+)$/', $value);
    }

    /**
     * 判断是否字母+数字
     * @param $value
     * @return bool
     */
    protected function validateAlphaNum($value): bool
    {
        return preg_match('/^([0-9a-zA-Z]+)$/', $value);
    }


    /**
     * 验证长度，若为数组则验证数组元素数，参数只有一个则判断是否相等
     * @param $value
     * @param $params
     * @return bool
     */
    protected function validateMinLen($value, $params): bool
    {
        $length = 0;
        $params = (int)$params;
        if (is_string($value)) {
            $length = mb_strlen($value);
        } else if (is_array($value)) {
            $length = count($value);
        }
        return $params <= $length;
    }

    protected function validateMaxLen($value, $params): bool
    {
        $length = 0;
        $params = (int)$params;
        if (is_string($value)) {
            $length = mb_strlen($value);
        } else if (is_array($value)) {
            $length = count($value);
        }
        return $params >= $length;
    }

    /**
     * 字符串长度验证
     * @param $value
     * @param $params
     * @return bool
     */
    protected function validateLen($value, $params): bool
    {
        return mb_strlen($value) === $params;
    }

    /**
     * 判断是否float
     * @param $value
     * @return bool
     */
    protected function validateFloat($value): bool
    {
        return (bool)filter_var($value, FILTER_VALIDATE_EMAIL);
    }

    /**判断是否布尔值
     * @param $value
     * @return bool
     */
    protected function validateBool($value): bool
    {
        return (bool)filter_var($value, FILTER_VALIDATE_BOOLEAN);
    }

    /**
     * 判断是否为IP
     * @param $value
     * @return bool
     */
    protected function validateIp($value): bool
    {
        return (bool)filter_var($value, FILTER_VALIDATE_IP);
    }

    /**
     * 判断是否是数字
     * @param $value
     * @return bool
     */
    protected function validateNumber($value): bool
    {
        return is_numeric($value);
    }

    private function getRules()
    {
        return $this->rules();
    }

    /**
     * 批量检测数据是否合法
     * @param  $data
     * @return bool
     * @throws ValidateException
     */
    public function check($data): bool
    {
        $this->data = $data;
        $rules = $this->getRules();
        foreach ($rules as $field => $rule) {
            if (is_array($rule)) {
                //如果设置了场景则判断场景是否和规则相符合
                if ($this->scene) {
                    //没有指定场景或者场景与设置的不同的规则跳过
                    if (!isset($rule['scene']) || $this->scene != $rule['scene'])
                        continue;
                }
                $rule = $rule[0];
            }

            //如果字段使用了 xxxx|bbbb 的形式，那么优先使用bbbb作为xxxx的本地化字段名称
            if (strpos($field, '|') !== false)
                [$field, $fieldName] = explode('|', $field, 2);
            else
                $fieldName = $this->friendlyName[$field] ?? $field;


            $rules = explode("|", $rule);

            if ($rules[0] == 'require') {
                if (!isset($data[$field]) || $data[$field] === '') {
                    $this->setErrMsg($field, 'require', '', $fieldName);
                    return false;
                }
                array_shift($rules);
            }

            if (!isset($data[$field]))
                continue;

            if (!$this->valid($data[$field], $field, $rules, $fieldName))
                return false;
        }
        return true;
    }


    /**
     * 验证数据 （普通验证）
     * @param null $value
     * @param null $filed
     * @param array $rules
     * @param $fieldName
     * @return bool
     */
    public function valid($value, $filed, $rules = [], $fieldName = null)
    {
        foreach ($rules as $rule) {
            $params = '';
            if (strpos($rule, ":")) {
                [$rule, $params] = explode(":", $rule, 2);
            }
            $validate = 'validate' . ucfirst($rule);
            if (!$this->$validate($value, $params)) {
                $this->setErrMsg($filed, $rule, $params, $fieldName);
                return false;
            }
        }
        return true;
    }
}